package cn.elves.model.rule.service.impl;

import cn.elves.model.rule.entity.constant.RootNodeConstants;
import cn.elves.model.rule.entity.dto.RuleTreeDTO;
import cn.elves.model.rule.entity.dto.RuleTreeNodeDTO;
import cn.elves.model.rule.entity.dto.RuleTreeNodeLineDTO;
import cn.elves.model.rule.entity.po.RuleTree;
import cn.elves.model.rule.entity.po.RuleTreeNode;
import cn.elves.model.rule.entity.po.RuleTreeNodeLine;
import cn.elves.model.rule.mapper.RuleTreeMapper;
import cn.elves.model.rule.service.RuleTreeNodeLineService;
import cn.elves.model.rule.service.RuleTreeNodeService;
import cn.elves.model.rule.service.RuleTreeService;
import cn.elves.model.rule.service.engine.DecisionResult;
import cn.elves.model.rule.service.engine.EngineRulesHolder;
import cn.elves.model.rule.service.engine.impl.RuleEngine;
import cn.elves.model.rule.service.rules.BaseRule;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author pc
 * @description 针对表【rule_tree】的数据库操作Service实现
 * @createDate 2024-06-16 12:48:17
 */
@Service
public class RuleTreeServiceImpl extends ServiceImpl<RuleTreeMapper, RuleTree>
        implements RuleTreeService {

    @Resource
    private RuleTreeMapper ruleTreeMapper;

    @Resource
    private RuleTreeNodeService ruleTreeNodeService;

    @Resource
    private RuleTreeNodeLineService ruleTreeNodeLineService;

    @Resource
    private EngineRulesHolder engineRulesHolder;

    @Resource
    private RuleEngine ruleEngine;

    @Override
    public void saveRuleTree(RuleTreeDTO ruleTreeDTO) {
        RuleTree ruleTree = new RuleTree();
        BeanUtils.copyProperties(ruleTreeDTO, ruleTree);
        ruleTree.setTreeRootNodeId(RootNodeConstants.ROOT_NODE_ID);
        ruleTreeMapper.insert(ruleTree);
    }

    @Override
    public void updateRuleTree(RuleTreeDTO ruleTreeDTO) {
        RuleTree ruleTreeDesc = new RuleTree();
        BeanUtils.copyProperties(ruleTreeDTO, ruleTreeDesc);
        ruleTreeMapper.updateById(ruleTreeDesc);
        RuleTreeNodeDTO rootNode = ruleTreeDTO.getRootNode();
        ruleTreeNodeService.remove(new QueryWrapper<RuleTreeNode>().eq("tree_id", ruleTreeDesc.getId()));
        ruleTreeNodeLineService.remove(new QueryWrapper<RuleTreeNodeLine>().eq("tree_id", ruleTreeDesc.getId()));
        if (rootNode == null) {
            return;
        }
        Queue<RuleTreeNodeLineDTO> ruleTreeNodeLineQueue = new ArrayDeque<>(rootNode.getRuleTreeNodeLineDTO());
        List<RuleTreeNodeDTO> ruleTreeNodeDTOList = new ArrayList<>();
        ruleTreeNodeDTOList.add(rootNode);
        //收集所有node
        while (ruleTreeNodeLineQueue.peek() != null) {
            RuleTreeNodeLineDTO line = ruleTreeNodeLineQueue.poll();
            RuleTreeNodeDTO ruleTreeNodeDTO = line.getRuleTreeNodeDTO();
            ruleTreeNodeDTOList.add(ruleTreeNodeDTO);
            List<RuleTreeNodeLineDTO> ruleTreeNodeLineDTO = ruleTreeNodeDTO.getRuleTreeNodeLineDTO();
            if (CollectionUtils.isNotEmpty(ruleTreeNodeLineDTO)) {
                ruleTreeNodeLineQueue.addAll(ruleTreeNodeLineDTO);
            }
        }
        Map<RuleTreeNode, RuleTreeNodeDTO> nodeMap = new HashMap<>();

        for (RuleTreeNodeDTO ruleTreeNodeDTO : ruleTreeNodeDTOList) {
            RuleTreeNode ruleTreeNode = new RuleTreeNode();
            BeanUtils.copyProperties(ruleTreeNodeDTO, ruleTreeNode);
            ruleTreeNode.setTreeId(ruleTreeDesc.getId());
            nodeMap.put(ruleTreeNode, ruleTreeNodeDTO);
        }
        ruleTreeNodeService.saveBatch(nodeMap.keySet());

        for (RuleTreeNode ruleTreeNode : nodeMap.keySet()) {
            RuleTreeNodeDTO ruleTreeNodeDTO = nodeMap.get(ruleTreeNode);
            ruleTreeNodeDTO.setId(ruleTreeNode.getId());
        }

        List<RuleTreeNodeLineDTO> ruleTreeNodeLineDTOList = new ArrayList<>();
        for (RuleTreeNode ruleTreeNode : nodeMap.keySet()) {
            RuleTreeNodeDTO ruleTreeNodeDTO = nodeMap.get(ruleTreeNode);
            for (RuleTreeNodeLineDTO ruleTreeNodeLineDTO : ruleTreeNodeDTO.getRuleTreeNodeLineDTO()) {
                ruleTreeNodeLineDTO.setNodeIdFrom(ruleTreeNodeDTO.getId());
                ruleTreeNodeLineDTO.setNodeIdTo(ruleTreeNodeLineDTO.getRuleTreeNodeDTO().getId());

            }
            ruleTreeNodeLineDTOList.addAll(ruleTreeNodeDTO.getRuleTreeNodeLineDTO());
        }
        List<RuleTreeNodeLine> ruleTreeNodeLineList = ruleTreeNodeLineDTOList.stream().map(item -> {
            RuleTreeNodeLine ruleTreeNodeLine = new RuleTreeNodeLine();
            BeanUtils.copyProperties(item, ruleTreeNodeLine);
            ruleTreeNodeLine.setTreeId(ruleTreeDesc.getId());
            return ruleTreeNodeLine;
        }).collect(Collectors.toList());
        ruleTreeNodeLineService.saveBatch(ruleTreeNodeLineList);

    }

    @Override
    public RuleTreeDTO queryRuleTree(Long treeId) {

        RuleTree ruleTree = ruleTreeMapper.selectById(treeId);

        List<RuleTreeNodeLine> nodeLineList = ruleTreeNodeLineService.list(new QueryWrapper<RuleTreeNodeLine>().eq("tree_id", treeId));

        List<RuleTreeNode> nodeList = ruleTreeNodeService.list(new QueryWrapper<RuleTreeNode>().eq("tree_id", treeId));

        return buildRuleTree(nodeList, nodeLineList, ruleTree);

    }

    private RuleTreeDTO buildRuleTree(List<RuleTreeNode> nodeList, List<RuleTreeNodeLine> nodeLineList, RuleTree ruleTree) {
        List<RuleTreeNodeDTO> nodeDTOS = nodeList.stream().map(item -> {
            RuleTreeNodeDTO ruleTreeNodeDTO = new RuleTreeNodeDTO();
            BeanUtils.copyProperties(item, ruleTreeNodeDTO);
            return ruleTreeNodeDTO;
        }).collect(Collectors.toList());

        List<RuleTreeNodeLineDTO> nodeLineDTOS = nodeLineList.stream().map(item -> {
            RuleTreeNodeLineDTO ruleTreeNodeLineDTO = new RuleTreeNodeLineDTO();
            BeanUtils.copyProperties(item, ruleTreeNodeLineDTO);
            return ruleTreeNodeLineDTO;
        }).collect(Collectors.toList());

        Map<Long, List<RuleTreeNodeLineDTO>> fromNodeLineMap = nodeLineDTOS.stream().collect(Collectors.groupingBy(RuleTreeNodeLineDTO::getNodeIdFrom));
        Map<Long, List<RuleTreeNodeLineDTO>> toNodeLineMap = nodeLineDTOS.stream().collect(Collectors.groupingBy(RuleTreeNodeLineDTO::getNodeIdTo));

        for (RuleTreeNodeDTO nodeDTO : nodeDTOS) {
            List<RuleTreeNodeLineDTO> fromLine = fromNodeLineMap.get(nodeDTO.getId());
            nodeDTO.setRuleTreeNodeLineDTO(fromLine);

            List<RuleTreeNodeLineDTO> toLine = toNodeLineMap.get(nodeDTO.getId());
            for (RuleTreeNodeLineDTO ruleTreeNodeLineDTO : toLine) {
                ruleTreeNodeLineDTO.setRuleTreeNodeDTO(nodeDTO);
            }
        }

        RuleTreeNodeDTO rootNode = nodeDTOS.stream().filter(item -> {
            return item.getId().equals(ruleTree.getTreeRootNodeId());
        }).findFirst().orElse(new RuleTreeNodeDTO());

        RuleTreeDTO ruleTreeDTO = new RuleTreeDTO();
        BeanUtils.copyProperties(ruleTree, ruleTreeDTO);
        ruleTreeDTO.setRootNode(rootNode);
        return ruleTreeDTO;
    }


    @Override
    public void addRule(String ruleKey) throws Exception {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(BaseRule.class);
        BaseRule rule = (BaseRule) enhancer.create();
        Class<BaseRule> baseRuleClass = BaseRule.class;
        Field ruleKeyValue = baseRuleClass.getDeclaredField("ruleKey");
        ruleKeyValue.setAccessible(true);
        ruleKeyValue.set(rule, ruleKey);
        Boolean b = engineRulesHolder.addRule(rule);
        if (b) {
            throw new DuplicateKeyException("重复规则被添加" + ruleKey);
        }
    }

    @Override
    public String doDecision( Long treeId,Map<String, Object> matter) {
        DecisionResult decisionResult = ruleEngine.process(treeId, matter);
        if (!decisionResult.isSuccess()) {
            throw new RuntimeException("决策异常");
        }
        return decisionResult.getNodeValue();
    }


}




